package compilers.table;

import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

import org.antlr.runtime.Token;

import compilers.node.Identifier;
import compilers.output.ErrorOutput;
import compilers.output.WarningOutput;
import compilers.stmt.FunctionType;
import compilers.types.Type;
import compilers.types.VoidType;

public class SymbolTable {
	Scope fScope;
	Map<String, Type> fTypes = new HashMap<String, Type>();
	Vector<FunctionType> fFunctions = new Vector<FunctionType>();
	
	public SymbolTable() {
		fScope = new Scope(null);
	}
	
	public void setScope(Scope scope) {
		fScope = scope;
	}
	
	public Scope getScope() {
		return fScope;
	}
	
	public Type resolveType(String name, Token token) {
		Type t = fTypes.get(name);
		if (t == null) {
			new ErrorOutput(token, "Type doesn't exist.");
		}
		return t;
	}
	
	public Type resolveVariable(String name, Token token, boolean array) {
		if (array && !fScope.resolveVariable(name, token).isArray()) {
			new ErrorOutput(token, "Can't resolve variable " + name + " (array vs ident)");
		}
		return fScope.resolveVariable(name, token);
	}
	
	public void checkMain() {
		boolean mainFound = false;
		for(int i=0;i<fFunctions.size();i++) {
			if(fFunctions.get(i).getfName().getfIdentifier().equals("main")) {
				mainFound = true;
			}
			
			if(!fFunctions.get(i).body()) {
				new ErrorOutput("Function " + fFunctions.get(i).getfName().getfIdentifier() + " has no body");
			}
		}
		
		if(mainFound) return;
		
		new ErrorOutput("No main function in file");
	}
	
	public FunctionType resolveFunction(String name, Token token) {
		for (int i = 0; i < fFunctions.size(); i++) {
			if (fFunctions.get(i).getfName().getfIdentifier().compareTo(name) == 0) {
				if (fFunctions.get(i).body()) {
					return fFunctions.get(i);
				}
			}
		}
		
		for (int i = 0; i < fFunctions.size(); i++) {
			if (fFunctions.get(i).getfName().getfIdentifier().compareTo(name) == 0) {
				return fFunctions.get(i);
			}
		}
		
		new ErrorOutput(token, "Function doesn't exist.");
		return null;
	}
	
	public void addType(String name, Type type) {
		fTypes.put(name, type);
	}
	
	public void checkFunctionBody(Token token, String name) {
		boolean b = false;
		for (int i = 0; i < fFunctions.size(); i++) {
			if ((fFunctions.get(i).getfName().getfIdentifier().compareTo(name) == 0) && (fFunctions.get(i).body())) {
				b = true;
			}
		}
		if (!b) {
	    	//new WarningOutput(token, "Function " + name + " has no function body at the moment it's called.");
		}
	}
	
	public void checkFunctionParam(String name, FunctionType type, Token token) {
		fFunctions.remove(type);

		for (int j = 0; j < fFunctions.size(); j++) {
		    FunctionType t = fFunctions.get(j);
		    
		    // If same name
		    if (t.getfName().getfIdentifier().compareTo(name) == 0) {
		    	if (t.parameterCount() == type.parameterCount()) {
		    		boolean same = true;
		    		for (int i = 0; i < type.getfParameters().size(); i++) {
		    			if (type.getfParameters().get(i).getType().getClass() != t.getfParameters().get(i).getType().getClass()) {
							same = false;
				    		new ErrorOutput(token, "Type of parameter should be the same in declaration and definition.");
						}
		    		}
		    		if (same) {
		    			if (!((!t.body() && type.body()) || (t.body() && !type.body()))) {
			    			new ErrorOutput(token, "Duplicate function.");
		    			} else {
		    				for (int i = 0; i < type.getfParameters().size(); i++) {
		    					if (!((type.getfParameters().get(i).getIdentifier() == null) || (t.getfParameters().get(i).getIdentifier() == null))) {
					    			if (type.getfParameters().get(i).getIdentifier().getfIdentifier().compareTo(t.getfParameters().get(i).getIdentifier().getfIdentifier()) != 0) {
							    		new ErrorOutput(token, "Name of parameter should be the same in declaration and definition.");
									}
		    					}
				    		}
		    			}
		    				
		    			if(type.getReturnType().getClass() != t.getReturnType().getClass()) {
				    		new ErrorOutput(token, "Return type doesn't match in declaration and definition.");
		    			}
		    		}
		    	} else {
		    		new ErrorOutput(token, "Number of parameters should be the same in declaration and definition.");
		    	}
		    	t.setBody();
			    return;
		    }
		}
		fFunctions.add(type);
	}
	
	public void addFunction(String name, FunctionType type, Token token) {
		fFunctions.add(type);
	}
	
	public void checkTypes(Type type1, Type type2, boolean init, Token token) {
		if ((!init) && (type1.isConstant())) {
			new ErrorOutput(token, "Can't assign to constant.");
		}
		if (!type1.getClass().isInstance(type2)) {
			if (type2 instanceof VoidType) {
				new ErrorOutput(token,"Can't assign variable, function has to be non-void.");
			}
			new WarningOutput(token, "Types assigned but with possible data loss.");
		}
	}
	
	public void checkArrayInit(int count, Identifier ident, Token token) {
		Vector<Integer> indices = ident.getIndices(token);
		if (!indices.isEmpty()) {
			int size = indices.get(0);
			for (int i=1; i<indices.size(); i++) {
				Integer integer = indices.get(i);
				size *= integer.intValue();
			}
			if (size < count) {
				new ErrorOutput(token, "Array overinitialisation");
			}
		}
	}

}
